﻿using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using HDM.NexusEngine.Core;
using HDM.NexusEngine.PathFinding;

namespace HDM.NexusEngine.Editor.Persist
{
    /// <summary>
    /// Save and load 0.1 versions of the game
    /// </summary>
    public class Persist
    {
        #region Saving

        internal static void Save(string path, Scene scene, TextureManager textureManager)
        {
            // Create a big string as the XML data 
            var saveData = new StringBuilder();
            saveData.AppendLine("<gamedata>");
            {
                // Save the texture data (at some point associate with scene / when used)
                saveData.AppendLine("\t<texturedata>");
                {
                    foreach (var textureEntry in textureManager)
                    {
                        saveData.AppendFormat("\t\t<entry key=\"{0}\" value=\"{1}\" />\n",
                            textureEntry.Key, textureEntry.Value.Path);
                    }
                }
                saveData.AppendLine("\t</texturedata>");
                // Save the scene
                saveData.AppendLine("\t<scenedata>");
                {
                    // there's only one scene
                    SaveScene(scene, 2, saveData);
                }
                saveData.AppendLine("\t</scenedata>");
            }
            saveData.AppendLine("</gamedata>");
            File.WriteAllText(path, saveData.ToString());
        }

        private static void SaveScene(Scene scene, int indentLevel, StringBuilder xmlData)
        {
            xmlData.AppendLine(new string('\t', indentLevel) + "<scene>");
            {
                indentLevel++;
                xmlData.AppendLine(new string('\t', indentLevel) + "<navmesh>");
                {
                    indentLevel++;
                    SaveNavMeshPolygons(scene.NavMesh, indentLevel, xmlData);
                    SaveNavMeshLinks(scene.NavMesh, indentLevel, xmlData);
                    indentLevel--;
                }
                xmlData.AppendLine(new string('\t', indentLevel) + "</navmesh>");
                indentLevel--;

                indentLevel++;
                xmlData.AppendLine(new string('\t', indentLevel) + "<layers>");
                {
                    indentLevel++;
                    foreach (var layer in scene.Layers)
                    {
                        xmlData.AppendFormat(new string('\t', indentLevel) +
                            "<layer name=\"{0}\" textureid=\"{1}\" />",
                            layer.Name, layer.TextureId);
                    }
                    indentLevel--;
                }
                xmlData.AppendLine(new string('\t', indentLevel) + "</layers>");
                indentLevel--;
            }
            xmlData.AppendLine(new string('\t', indentLevel) + "</scene>");
        }

        private static int FindPolygonIndex(NavMesh navMesh, ConvexPolygon convexPolygon)
        {
            return navMesh.PolygonList.FindIndex(convexPolygon.Equals);
        }

        private static void SaveNavMeshLinks(NavMesh navMesh, int indentLevel, StringBuilder xmlData)
        {
            xmlData.AppendLine(new string('\t', indentLevel) + "<links>");
            {
                indentLevel++;
                foreach (var link in navMesh.Links)
                {
                    xmlData.AppendLine(new string('\t', indentLevel) + "<link>");
                    {
                        indentLevel++;

                        xmlData.AppendFormat(new string('\t', indentLevel) +
                            "<start polygon=\"{0}\" edgestart=\"{1}\" edgeend=\"{2}\"/>\n",
                            FindPolygonIndex(navMesh, link.StartPoly),
                            link.StartEdgeIndex.Start,
                            link.StartEdgeIndex.End);

                        xmlData.AppendFormat(new string('\t', indentLevel) +
                            "<end polygon=\"{0}\" edgestart=\"{1}\" edgeend=\"{2}\"/>\n",
                            FindPolygonIndex(navMesh, link.EndPoly),
                            link.EndEdgeIndex.Start,
                            link.EndEdgeIndex.End);

                        indentLevel--;
                    }
                    xmlData.AppendLine(new string('\t', indentLevel) + "</link>");
                }
                indentLevel--;
            }
            xmlData.AppendLine(new string('\t', indentLevel) + "</links>");
        }

        private static void SaveNavMeshPolygons(NavMesh navMesh, int indentLevel, StringBuilder xmlData)
        {
            xmlData.AppendLine(new string('\t', indentLevel) + "<polygons>");
            {
                indentLevel++;
                foreach (var polygon in navMesh.PolygonList)
                {
                    xmlData.AppendLine(new string('\t', indentLevel) + "<polygon>");
                    {
                        indentLevel++;
                        foreach (var point in polygon.Vertices)
                        {
                            xmlData.AppendFormat(new string('\t', indentLevel) +
                                "<point x=\"{0}\" y =\"{1}\"/>",
                                point.X, point.Y);
                        }
                        indentLevel--;
                    }
                    xmlData.AppendLine(new string('\t', indentLevel) + "</polygon>");
                }
                indentLevel--;
            }
            xmlData.AppendLine(new string('\t', indentLevel) + "</polygons>");
        }

        #endregion

        #region Loading

        internal static void Open(string path, Scene scene, TextureManager textureManager, Layers layers)
        {
            var xmlReader = new XmlTextReader(path);
            xmlReader.MoveToContent();  // Jumps into top level header
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "texturedata":
                        LoadTextureData(xmlReader, textureManager);
                        break;
                    case "scenedata":
                        LoadSceneData(xmlReader, scene, textureManager);
                        break;
                }
            }
            layers.RefreshLayerContent(scene.Layers);
        }

        private static void LoadSceneData(XmlTextReader xmlReader, Scene scene, TextureManager textureManager)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "scene":
                        LoadSingleScene(xmlReader, scene, textureManager);
                        break;
                    case "sceendata":
                        return;
                }
            }
        }

        private static void LoadSingleScene(XmlTextReader xmlReader, Scene scene, TextureManager textureManager)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "navmesh":
                        LoadNavMesh(xmlReader, scene);
                        break;
                    case "layers":
                        LoadLayers(xmlReader, scene, textureManager);
                        break;
                    case "scene":
                        return;
                }
            }
        }

        private static void LoadLayers(XmlTextReader xmlReader, Scene scene, TextureManager textureManager)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "layer":
                    {
                        var name = xmlReader.GetAttribute("name");
                        var textureId = xmlReader.GetAttribute("textureid");
                        var layer = new Layer(name);
                        layer.SetImage(textureManager.Get(textureId));
                        scene.AddLayer(layer);
                    }
                        break;
                    case "layers":
                        return;
                }
            }
        }

        private static void LoadNavMesh(XmlTextReader xmlReader, Scene scene)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "polygons":
                        LoadPolygons(xmlReader, scene.NavMesh);
                        break;
                    case "links":
                        LoadLinks(xmlReader, scene.NavMesh);
                        break;
                    case "navmesh":
                        return;
                }
            }
        }

        private static void LoadLinks(XmlTextReader xmlReader, NavMesh navMesh)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "link":
                        LoadSingleLink(xmlReader, navMesh);
                        break;
                    case "links":
                        return;
                }
            }
        }

        /// <summary>
        /// This function will look up the polygon index right away. 
        /// This could potentially cause problems if the XML has the polygon defs later in the file.
        /// The values should really be cached and it should be done after.
        /// (or thrown in a closure but the C# syntax is a little too messy for that)
        /// </summary>
        /// <param name="xmlReader"></param>
        /// <param name="navMesh"></param>
        private static void LoadSingleLink(XmlTextReader xmlReader, NavMesh navMesh)
        {
            var startPolygonIndex = -1;
            var startEdgeStart = -1;
            var startEdgeEnd = -1;

            var endPolygonIndex = -1;
            var endEdgeStart = -1;
            var endEdgeEnd = -1;

            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                string polygon;
                string edgestart;
                string edgeend;

                switch (xmlReader.Name)
                {
                    case "start":
                        polygon = xmlReader.GetAttribute("polygon");
                        edgestart = xmlReader.GetAttribute("edgestart");
                        edgeend = xmlReader.GetAttribute("edgeend");

                        int.TryParse(polygon, out startPolygonIndex);
                        int.TryParse(edgestart, out startEdgeStart);
                        int.TryParse(edgeend, out startEdgeEnd);
                        break;
                    case "end":
                        polygon = xmlReader.GetAttribute("polygon");
                        edgestart = xmlReader.GetAttribute("edgestart");
                        edgeend = xmlReader.GetAttribute("edgeend");

                        int.TryParse(polygon, out endPolygonIndex);
                        int.TryParse(edgestart, out endEdgeStart);
                        int.TryParse(edgeend, out endEdgeEnd);
                        break;
                    case "link":
                    {
                        var polygonLink = new PolygonLink(
                            navMesh.PolygonList[startPolygonIndex],
                            new IndexedEdge(startEdgeStart, startEdgeEnd),
                            navMesh.PolygonList[endPolygonIndex],
                            new IndexedEdge(endEdgeStart, endEdgeEnd));
                        navMesh.AddLink(polygonLink);
                        return;
                    }
                }
            }
        }

        private static void LoadPolygons(XmlTextReader xmlReader, NavMesh navMesh)
        {
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "polygon":
                        LoadSinglePolygon(xmlReader, navMesh);
                        break;
                    case "polygons":
                        return;
                }
            }
        }

        private static void LoadSinglePolygon(XmlReader xmlReader, NavMesh navMesh)
        {
            if (xmlReader == null) throw new ArgumentNullException("xmlReader");

            var polygon = new ConvexPolygon();
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "point":
                    {
                        float x;
                        float y;
                        var xAttribute = xmlReader.GetAttribute("x");
                        var yAttribute = xmlReader.GetAttribute("y");

                        float.TryParse(xAttribute, out x);
                        float.TryParse(yAttribute, out y);
                        polygon.Vertices.Add(new Point(x, y));
                    }
                        break;
                    case "polygon":
                        polygon.GenerateEdges();
                        navMesh.AddPolygon(polygon);
                        return;
                }
            }
        }

        private static void LoadTextureData(XmlTextReader xmlReader, TextureManager textureManager)
        {
            if (xmlReader == null) throw new ArgumentNullException("xmlReader");

            // Jump into the texture data node
            xmlReader.MoveToContent();
            while (xmlReader.Read())
            {
                switch (xmlReader.Name)
                {
                    case "entry":
                    {
                        var key = xmlReader.GetAttribute("key");
                        var value = xmlReader.GetAttribute("value");
                        if (textureManager.Exists(key))
                        {
                            MessageBox.Show(@"Warning a texture is trying to be loaded twice. This shouldn't ever happen.");
                        }
                        else
                        {
                            textureManager.LoadTexture(key, value);
                        }
                    }
                        break;
                    case "texturedata":
                        return;
                }

            }
        }

        #endregion
    }
}